﻿namespace MyCompany.Models {
	using System;
	using System.Diagnostics;

	using MyCompany.Core;

	using SolidMvvm;

	using SolidMvvmInterfaces;

	using WebSocket4Net;

	/// <summary>
	///     A wrapper for a WebSocket4Net server.
	/// </summary>
	public class WebSocketClient : ModelBase {
		public delegate void ReceivedBytesDelegate(WebSocketClient sender, byte[] bytes);

		public delegate void ReceivedTextDelegate(WebSocketClient sender, string message);

		WebSocket webSocket;

		/// <summary>Constructor</summary>
		/// <param name="localIPAddressWithoutWebSocketPrefix">For example, "10.0.1.147".</param>
		/// <param name="port">A local-host port different than the HTTP server.</param>
		/// <param name="messageReporter">
		///     Any message reporter. It will only be invoked on the owning thread,
		///     so assuming that it's the UI thread, it's safe to put the message in the GUI.
		/// </param>
		/// <param name="receivedTextDelegate">Called when text is received over the socket.</param>
		public WebSocketClient(string localIPAddressWithoutWebSocketPrefix, int port, IMessageReporter messageReporter, ReceivedTextDelegate receivedTextDelegate, ReceivedBytesDelegate receivedBytesDelegate) {
			try {
				MessageReporter = messageReporter;
				LocalIPAddressWithoutWebSocketPrefix = localIPAddressWithoutWebSocketPrefix;
				Port = port;
				ReceivedText = receivedTextDelegate;
				ReceivedBytes = receivedBytesDelegate;

				webSocket = new WebSocket(Utilities.GetFullWebSocketAddressWithPort(localIPAddressWithoutWebSocketPrefix, port));

				webSocket.Opened += (sender, e) => MessageReporter.ReportMessage("Socket connection at " + Utilities.GetFullWebSocketAddressWithPort(localIPAddressWithoutWebSocketPrefix, port) + " opened.", null, MessageType.Console);

				webSocket.Error += (sender, e) => MessageReporter.ReportMessage("Error on socket connection at " + Utilities.GetFullWebSocketAddressWithPort(localIPAddressWithoutWebSocketPrefix, port) + ": " + e.Exception.Message, e.Exception.ToString(), MessageType.Console);

				webSocket.Closed += (sender, e) => MessageReporter.ReportMessage("Socket connection at " + Utilities.GetFullWebSocketAddressWithPort(localIPAddressWithoutWebSocketPrefix, port) + " closed.", null, MessageType.Console);

				webSocket.MessageReceived += (sender, e) => {
					ReceivedText(this, e.Message);
					MessageReporter.ReportMessage("Text received: " + e.Message, null, MessageType.Console);
				};

				webSocket.DataReceived += (sender, e) => {
					ReceivedBytes(this, e.Data);
					MessageReporter.ReportMessage("Bytes received: " + Conversions.ByteArrayToHexString(e.Data), null, MessageType.Console);
				};

				webSocket.Open();

				MessageReporter.ReportMessage("WebSocket client connecting at " + Utilities.GetFullWebSocketAddressWithPort(localIPAddressWithoutWebSocketPrefix, port) + ".", null, MessageType.Console);
			}
			catch (Exception exception) {
				Cleaner.CleanConstructor(this, exception);
			}
		}

		public IMessageReporter MessageReporter { get; private set; }

		public ReceivedTextDelegate ReceivedText { get; private set; }

		public ReceivedBytesDelegate ReceivedBytes { get; private set; }

		public string LocalIPAddressWithoutWebSocketPrefix { get; private set; }

		public int Port { get; private set; }

		public override void Dispose() {
			GC.SuppressFinalize(this);

			var disposeErrors = String.Empty;

			if (webSocket != null)
				try {
					if (webSocket.State == WebSocketState.Open)
						webSocket.Close();
				}
				catch (Exception exception) {
					disposeErrors += exception.Message + "\n";
				}
			webSocket = null;

			MessageReporter = null;
			LocalIPAddressWithoutWebSocketPrefix = null;
			Port = -1;
			ReceivedText = null;
			ReceivedBytes = null;

			try {
				base.Dispose();
			}
			catch (Exception exception) {
				disposeErrors += exception.Message + "\n";
			}

			if (disposeErrors != String.Empty)
				throw new CleanupException(disposeErrors.Trim(), null);
		}

#if DEBUG
		~WebSocketClient() {
			Debugger.Break(); // Who forgot to dispose the server?
		}
#endif

		public void SendText(string message) {
			webSocket.Send(message);
		}

		public void SendBytes(byte[] bytes) {
			webSocket.Send(bytes, 0, bytes.Length);
		}
	}
}
